Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a function to save a slice in MDH format #149

Merged
merged 11 commits into from
Sep 12, 2023

Conversation

granrothge
Copy link

This function will take the output of a spectra and create a slice in an MDH format.
Note the "Instrument" name is hard coded as a valid instrument is needed for Mantid to read the file. I'd prefer this to be "SpinW", but since that is not a valid Mantid instrument it can't be read by Mantid. I would like to see this requirement relaxed in Mantid, but that is a different discussion.

@codecov-commenter
Copy link

codecov-commenter commented Aug 25, 2023

Codecov Report

Patch coverage: 21.89% and project coverage change: +0.22% 🎉

Comparison is base (415d143) 38.67% compared to head (32cb8b7) 38.90%.
Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #149      +/-   ##
==========================================
+ Coverage   38.67%   38.90%   +0.22%     
==========================================
  Files         239      240       +1     
  Lines       15829    15971     +142     
==========================================
+ Hits         6122     6213      +91     
- Misses       9707     9758      +51     
Files Changed Coverage Δ
swfiles/private/sw_issymspec.m 54.54% <0.00%> (-20.46%) ⬇️
swfiles/sw_instrument.m 2.36% <0.00%> (-0.16%) ⬇️
swfiles/sw_spec2MDHisto.m 0.00% <0.00%> (ø)
swfiles/sw_egrid.m 85.71% <100.00%> (+22.64%) ⬆️

... and 2 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@mducle mducle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks ok. But there are a couple of issues:

  1. I get an error when the output file already exists - could you either document this or automatically remove / overwrite the file? (I think this might be a Matlab HDF issue). Maybe do a if exist(filename, 'file'), delete(filename);?
  2. Could you move the read_struct function into the sw_spec2MDHisto.m file (I don't think it's used anywhere else and I would rather it not be in the root folder as a separate m-file).
  3. Could you add an example to the doc-text / help? It's a bit confusing at the moment. In particular, I would change the text to be something like:
% spectra: a structure calculated by sw_egrid
%     Only a single q-direction can be given in the call to spinwave
% proj: a 3x3 matrix where each column is a vector defining the orientation of the view.
%     One of the vectors must be along the q-axis used in the spinwave call 
% dproj: is a 3 vector that is the bin size in each direction
% filename: is the name of the nexus file.
%
% Example:
% q0 = [0 0 0];
% qdir = [1 0 0];
% spec = sw_egrid(spinwave(sw_model('triAF', 1), {q0 q0+qdir 100}))
% proj = [qdir(:) [0 1 0]' [0 0 1]'];
% dproj = [0.05, 0.05, 0.05];
% sw_spec2MDHisto(sp, proj, dproj, 'testmdh.nxs');
%
% Note that: 
% (1) In the call to `spinwave`, only one q-direction may be specified
%    e.g. the HKL specifier must be of the form {q0 q0+qdir nsteps}
% (2) one column in the `proj` matrix must be the q-direction used in 
%    `spinwave` (e.g. `qdir`).

Otherwise I tested with the following code:

swo = sw_model('triAF', 1);
sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 0 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 0 1 0; 0 0 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

And I could read the resulting testmdh.nxs file in Mantid 6.6, where it gives a 2D spectra along the Qh direction and the MDH files says that it's integrated between -0.005 and +0.005 along the Qk and Ql directions (which bin size is given by the dproj input I guess?).

However, when I use:

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 0 0] [0 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 0 1 0; 0 0 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

It gives the wrong spectrum - it plots the whole thing as if it was only along Qk.

And when I use:

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 1] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 0 1 0; 0 0 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

It gives an error, as does:

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 1] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 1 1 0; 1 0 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

So - does proj have to be a set of orthogonal axes?

In future is it possible to automatically calculate proj from the spectrum structure? (Maybe if the user gives one other q-direction / defines a scattering plane).

@granrothge
Copy link
Author

@mducle All Good points. I will update accordingly.

@granrothge
Copy link
Author

@mducle I think I have changed things to match the requested changed. Let me know if anything else is needed.

@granrothge granrothge requested a review from mducle September 1, 2023 22:39
@RichardWaiteSTFC
Copy link
Collaborator

Thanks for this @granrothge!
I just had a quick test and i think I still see the same issues as @mducle .

  1. With the slice going along two different symmetry directions
sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 0 0] [0 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 0 1 0; 0 0 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

it produces this workspace in mantid
image
i.e. it has 200 bins all along K.
I actually think with the limitations of MDHisto workspaces in mantid that we can't do better than that...do you have a requirement to support such slices? If not maybe we only support slices along a single Q direction? Or if you do need to support these slices we could print a warning to the user so they know what to expect in the .nxs file?

  1. The following code
sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 1] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 0 0; 0 1 0; 0 0 1], [0.01, 0.01, 0.01], 'testmdh3.nxs');

produces this error

Error using reshape
Number of elements must not change. Use [] as one of the size
inputs to automatically calculate the appropriate size for that
dimension.

Error in sw_spec2MDHisto (line 81)
signal = reshape(dat,Dszs);

@RichardWaiteSTFC
Copy link
Collaborator

RichardWaiteSTFC commented Sep 5, 2023

I was wondering does the proj matrix need to have the normalised unit vectors? Or maybe I'm mis-interpreting it...?
When I specify a slice along [110] from the doc string I gather 110 needs to be in the proj matrix, but when I try

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 1 0; -1 1 0; 0, 0, 1], [0.01, 0.01, 0.01], 'testmdh3.nxs');

I get the same error as above.
If I normalise the vectors in proj like so

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1/sqrt(2) 1/sqrt(2) 0; -1/sqrt(2) 1/sqrt(2) 0; 0, 0, 1], [0.01, 0.01, 0.01], 'testmdh3.nxs');

I don't get the error, but the algorithm seems to hang and the .nxs file is not closed (so can't be opened in mantid).
Am I doing something wrong? If so maybe some more info in the doc string and a more informative error would be helpful!

@granrothge
Copy link
Author

@RichardWaiteSTFC @mducle OK Let me do some more rigorous testing. As to question 1. I Do not envision doing more than one direction at a time. I will at least add some documentation and may add an error catch.

@granrothge
Copy link
Author

@mducle @RichardWaiteSTFC I think I have addressed the off axis case. Good catch. I also added a prototype of a test script that provides which directions should be tested. I'm not familiar with the testing setup so it will likely need to be modified to actually run.

@RichardWaiteSTFC
Copy link
Collaborator

Thanks for the changes - I think there may still be a bug with determining the x-axis of the slice from the proj matrix provided.
If I make this cut along [110]

swo = sw_model('triAF', 1);
sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1 1 0; -1 1 0; 0, 0, 1], [0.01, 0.01, 0.01], 'testmdh.nxs');

And plot it in mantid I get a cut along [1-10]
image

I think there may also be an issue with the normalisation of the basis vectors - or at least if you supply a normalised proj matrix when the argument for sw_egrid isn't normalised like so

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1/sqrt(2) 1/sqrt(2) 0; -1/sqrt(2) 1/sqrt(2) 0; 0, 0, 1], [0.01, 0.01, 0.01], 'testmdh3.nxs');
sw_plotspec(sp)

you get this
image
But of course the Bragg peak isn't at (0.7,0.7.0) it's at (1,1,0)...

Copy link
Collaborator

@RichardWaiteSTFC RichardWaiteSTFC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit test has a few bugs - but thanks for writing them!

function test_1_0_0(testCase)
q0 = [0 0 0];
qdir = [1 0 0];
spec = sw_egrid(spinwave(sw_model('triAF', 1), {q0 q0+qdir nsteps}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spinw model appears to be the same used for all tests. You can add this to a TestClassSetup or TestMethodSetup like so

methods (TestClassSetup)
function set_seed(testCase)
testCase.orig_rng_state = rng;
rng('default');
end
end
methods (TestMethodSetup)
function setup_chain_model(testCase)
testCase.swobj = spinw();
testCase.swobj.genlattice('lat_const', [3 8 8])
testCase.swobj.addatom('r',[0; 0; 0],'S',1)
testCase.swobj.gencoupling();
end
end

In this case TestClassSetup is probably best as it isn't changed in any of the tests.

% `spinwave` (e.g. `qdir`).


if nargin==0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically add a unit test for this like so

function test_no_input_calls_help(testCase)
% Tests that if call addmatrix with no input, it calls the help
help_function = sw_tests.utilities.mock_function('swhelp');
testCase.swobj.addatom();
testCase.assertEqual(help_function.n_calls, 1);
testCase.assertEqual(help_function.arguments, ...
{{'spinw.addatom'}});
end

dproj = [norm((qdir-q0))/nsteps, 1e-6, 1e-6];
sw_spec2MDHisto(spec, proj, dproj, 'tmp/test112mdh.nxs');
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be another end here.

function test_1_0_0(testCase)
q0 = [0 0 0];
qdir = [1 0 0];
spec = sw_egrid(spinwave(sw_model('triAF', 1), {q0 q0+qdir nsteps}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More importantly when I add the missing end and run the tests they fail because nsteps isn't defined!

@RichardWaiteSTFC
Copy link
Collaborator

RichardWaiteSTFC commented Sep 7, 2023

I think there may also be an issue with the normalisation of the basis vectors - or at least if you supply a normalised proj matrix when the argument for sw_egrid isn't normalised like so

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1/sqrt(2) 1/sqrt(2) 0; -1/sqrt(2) 1/sqrt(2) 0; 0, 0, 1], [0.01, 0.01, 0.01], 'testmdh3.nxs');
sw_plotspec(sp)

you get this

image

But of course the Bragg peak isn't at (0.7,0.7.0) it's at (1,1,0)...

Ah having looked at the doc string I think I needed to update the dproj argument - I tried this

sp = sw_egrid(sw_neutron(spinwave(swo, {[0 0 0] [1 1 0] 100})),'component','Sperp', 'Evect', linspace(0,5,200));
sw_spec2MDHisto(sp, [1/sqrt(2) 1/sqrt(2) 0; -1/sqrt(2) 1/sqrt(2) 0; 0, 0, 1], [0.01*sqrt(2), 0.01, 0.01], 'testmdh3.nxs');

but I got the same result - I would expect the end x value to be sqrt(2)

@granrothge
Copy link
Author

I thought I had fixed the issue with the negative axis. I will look into it as well as the normalization issues.

@granrothge
Copy link
Author

Aah here is the problem with the projection. It must be in columns not rows.

proj = [[1 1 0]' [1 -1 0]' [0 0 1]']
ans =

     1     1     0
     1    -1     0
     0     0     1

not

[1 1 0; -1 1 0; 0, 0, 1]

ans =

     1     1     0
    -1     1     0
     0     0     1

@RichardWaiteSTFC
Copy link
Collaborator

Aah here is the problem with the projection. It must be in columns not rows.

Sorry, it does say that in the doc string but I missed it! Thanks for clearing that up!

@granrothge
Copy link
Author

@RichardWaiteSTFC @mducle Ok So I think this is now as ready as I can get it.

  1. The test suite now runs and I expanded a few more cases. The files should be read back in and compared. With more understanding of the Matlab test facilities. the test code could be greatly reduced.

  2. I now check that all of the projection vectors are orthogonal to each other (There is a test for this).

  3. I only use the projection vector along the propagation direction to check which vector is the propagation direction. It is over written in the script so the one in the file comes from the spectra object rather than the projection matrix. Similarly the step size input for the propagation direction is over written by what is in the spectra object. I left these in for completeness. This accounts for the normalization issue of the x axis.

  4. the CI is failing because it does not know where to write the files.

Copy link
Member

@mducle mducle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Garrett, could you change the test file to this:

classdef unittest_spinw_spec2MDHisto < sw_tests.unit_tests.unittest_super

    properties
        swModel = [];
        tmpdir = '';
        testfilename = '';
        nsteps = {100};
    end

    properties (TestParameter)
        testpars = struct(...
            'test_1_0_0', struct('q0', [0 0 0], 'qdir', [1 0 0], 'proj', [[1 0 0]' [0 1 0]' [0 0 1]'], 'nxs', 'test100mdh.nxs'), ...
            'test_1_1_0', struct('q0', [0 0 0], 'qdir', [1 1 0], 'proj', [[1 1 0]' [1 -1 0]' [0 0 1]'], 'nxs', 'test110mdh.nxs'), ...
            'test_1_1_1', struct('q0', [0 0 0], 'qdir', [1 1 1], 'proj', [[1 1 1]' [1 -1 0]' [1 1 -2]'], 'nxs', 'test111mdh.nxs'), ...
            'test_1_1_2', struct('q0', [0 0 2], 'qdir', [1 1 0], 'proj', [[1 1 0]' [1 -1 0]' [0 0 1]'], 'nxs', 'test112mdh.nxs'), ...
            'test_1_1_2_2', struct('q0', [0 0 2], 'qdir', [1 1 0], 'proj', [[1 -1 0]' [1 1 0]' [0 0 1]'], 'nxs', 'test112_2mdh.nxs'), ...
            'test_2_2_2', struct('q0', [2 2 2], 'qdir', [1 1 0], 'proj', [[1 1 0]' [1 -1 0]' [0 0 1]'], 'nxs', 'test222mdh.nxs'));
    end

    methods (TestClassSetup)
        function setup_model(testCase)
            testCase.swModel = sw_model('triAF', 1);
        end
        function setup_tempdir(testCase)
            testCase.tmpdir = tempdir;
        end
    end

    methods (TestMethodTeardown)
        function remove_tmpdir(testCase)
            delete(testCase.testfilename);
        end
    end

    methods (Test)
        function test_qdirs(testCase, testpars)
            q0 = testpars.q0;
            qdir = testpars.qdir;
            proj = testpars.proj;
            testCase.disable_warnings('spinw:spinwave:NonPosDefHamiltonian');
            spec = sw_egrid(spinwave(testCase.swModel, {q0 q0+qdir testCase.nsteps{1}}));
            dproj = [norm((qdir-q0))/testCase.nsteps{1}, 1e-6, 1e-6];
            testCase.testfilename = fullfile(testCase.tmpdir, testpars.nxs);
            sw_spec2MDHisto(spec, proj, dproj, testCase.testfilename);
        end
        function test_non_ortho(testCase)
            q0 = [0 0 2];
            qdir = [1 1 0];
            spec = sw_egrid(spinwave(testCase.swModel, {q0 q0+qdir testCase.nsteps{1}}));
            proj = [qdir(:) [1 0 0]' [0 0 1]'];
            dproj = [1e-6, norm((qdir-q0))/testCase.nsteps{1}, 1e-6];
            verifyError(testCase,@() sw_spec2MDHisto(spec, proj, dproj, 'tmp/test_blank.nxs'), "read_struct:nonorthogonal")
        end
    end
end

To make it more compact and fix the filename issue.

Otherwise it looks fine to me.

@granrothge
Copy link
Author

@RichardWaiteSTFC I think it is ready to approve and go.

@RichardWaiteSTFC RichardWaiteSTFC merged commit ea5ea33 into SpinW:master Sep 12, 2023
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants